//    Copyright (C) Kherty.  All rights reserved.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Markup;
using System.Windows.Media;

namespace OpenLS.Spreadsheet.UI
{
    internal partial class WindowsClipboard
    {
        internal override Range Copied()
        {
            var clipboardDataObject = new RangeDataObject(Clipboard.GetDataObject());
            return clipboardDataObject.Range;
        }

        #region Nested type: RangeDataObject

        private class RangeDataObject
        {
            public readonly string Text;
            private readonly Range _range;
            public readonly DataObject dataObject;

            public RangeDataObject(Range range)
            {
                dataObject = new DataObject();
                foreach (string format in GetFormats())
                {
                    var memoryStream = new MemoryStream();
                    range.Save(memoryStream, format);
                    memoryStream.Position = 0;
                    dataObject.SetData(format, memoryStream);
                    if (format == DataFormats.Text)
                    {
                        memoryStream.Position = 0;
                        var reader = new StreamReader(memoryStream);
                        dataObject.SetText(reader.ReadToEnd());
                    }
                }
            }

            public RangeDataObject(IDataObject dataObject)
            {
                var stream = (Stream) dataObject.GetData(SpreadsheetDataFormats.SpreadsheetDocumentDataFormat);
                if (stream != null)
                {
                    _range = Range.RangeFromStream(stream, SpreadsheetDataFormats.SpreadsheetDocumentDataFormat);
                    return;
                }
#if NET40
                var biffStream = (Stream)dataObject.GetData(SpreadsheetDataFormats.Biff8);
                if (biffStream != null)
                {
                    _range = Range.RangeFromStream(biffStream, SpreadsheetDataFormats.Biff8);
                    return;
                }
#endif

                var xmlStream = (Stream) dataObject.GetData(SpreadsheetDataFormats.XmlSpreadsheetFormat);
                if (xmlStream != null)
                {
                    Debug.WriteLine("XML Spreadsheet found");
#if DEBUG_CLIPBOARD
                        using (TextReader r = new StreamReader(xmlStream))
                        {
                            Debug.WriteLine(r.ReadToEnd());
                        }
                        xmlStream = (Stream)dataObject.GetData("XML Spreadsheet");
#endif
                    _range = Range.RangeFromStream(xmlStream, SpreadsheetDataFormats.XmlSpreadsheetFormat);
                    return;
                }
                if (_range == null)
                {
                    var rtb = new RichTextBox();
                    rtb.Paste();
                    FlowDocument doc = rtb.Document;
                    SpreadsheetDocument document = SpreadsheetDocument.Create();
                    var worksheet = (Worksheet) document.Workbook.Sheets[0];
                    var importer = new XamlImporter(worksheet, rtb);
                    try
                    {
                        _range = importer.GetRange(rtb.Document);
                    }
                    catch
                    {
                    }
                }
                Text = (string) dataObject.GetData(typeof (string));
                Debug.WriteLine("end of RangeDataObject");
            }


            public Range Range
            {
                get { return _range; }
            }

            private IEnumerable<string> GetFormats()
            {
                yield return SpreadsheetDataFormats.SpreadsheetDocumentDataFormat;
                yield return DataFormats.Text;
                yield return DataFormats.Xaml;
                yield return DataFormats.Rtf;

                yield return DataFormats.CommaSeparatedValue;
#if NET40
                foreach (var format in FormatManager.DataFormats)
                    yield return format.ClipboardName;
                //yield return SpreadsheetDataFormats.Biff8;
#endif
            }

            #region Nested type: XamlImporter

            private class XamlImporter
            {
                private readonly RichTextBox _rtb;
                private readonly Worksheet _worksheet;

                private int _column;
                private int _maxColumn;
                private int row;

                public XamlImporter(Worksheet worksheet, RichTextBox rtb)
                {
                    _worksheet = worksheet;
                    _rtb = rtb;
                }

                private int column
                {
                    get { return _column; }
                    set
                    {
                        _column = value;
                        _maxColumn = Math.Max(_maxColumn, value);
                    }
                }

                protected Cell Cell
                {
                    get { return _worksheet[row, column]; }
                }

                internal Range GetRange(FlowDocument document)
                {
                    Debug.WriteLine(XamlWriter.Save(document));
                    foreach (Block block in document.Blocks)
                    {
                        var table = block as System.Windows.Documents.Table;
                        var para = block as Paragraph;
                        if (table != null)
                        {
                            foreach (TableRow tableRow in Enumerable.SelectMany(table.RowGroups, gr => gr.Rows))
                            {
                                foreach (TableCell tableCell in tableRow.Cells)
                                {
                                    ImportFormatting(tableCell);
                                    if (tableCell.Blocks.Count == 1)
                                    {
                                        processBlock(tableCell.Blocks.FirstBlock);
                                    }
                                    Thickness thickness = tableCell.BorderThickness;
                                    if (thickness != new Thickness() && tableCell.BorderBrush != null)
                                    {
                                        SpreadsheetColor color = ConvertForeground(tableCell.BorderBrush);
                                        Func<double, BorderStyle> GetStyle = (double d) =>
                                                                                 {
                                                                                     if (d == 0)
                                                                                         return BorderStyle.None;
                                                                                     if (d == 1)
                                                                                         return BorderStyle.Thin;
                                                                                     if (d == 2)
                                                                                         return BorderStyle.Thick;
                                                                                     return BorderStyle.Thin;
                                                                                 };
                                        Func<double, Border> GetBorder = (double d)
                                                                         =>
                                                                             {
                                                                                 if (d == 0)
                                                                                     return null;
                                                                                 return new Border(
                                                                                     GetStyle(thickness.Left), color);
                                                                             };
                                        Cell.Borders = new Borders(
                                            GetBorder(thickness.Left),
                                            GetBorder(thickness.Top),
                                            GetBorder(thickness.Right),
                                            GetBorder(thickness.Bottom));
                                    }
                                    column++;
                                }
                                column = 0;
                                row++;
                            }
                        }
                        else if (para != null)
                        {
                            processBlock(para);
                            row++;
                        }
                        else
                            throw new NotSupportedException();
                    }
                    return _worksheet.GetRange(_worksheet[0, 0], _worksheet[row, _maxColumn]);
                }

                private void ImportFormatting(TextElement textElement)
                {
                    if (textElement.Background != null)
                        Cell.Background = ConvertBackground(textElement.Background);
                    if (textElement.Foreground != null)
                        Cell.Foreground = ConvertForeground(textElement.Foreground);
                    Cell.FontSize = textElement.FontSize/4d*3d;
                    Cell.FontWeight = textElement.FontWeight;
                    Cell.FontStyle = textElement.FontStyle;
                    Cell.FontFamily = textElement.FontFamily;
                }

                private Fill ConvertBackground(Brush background)
                {
                    if (!(background is SolidColorBrush))
                        return null;
                    var br = (SolidColorBrush) background;
                    return new PatternFill(new RgbColor(br.Color));
                }

                private SpreadsheetColor ConvertForeground(Brush background)
                {
                    if (!(background is SolidColorBrush))
                        return null;
                    var br = (SolidColorBrush) background;
                    return new RgbColor(br.Color);
                }

                private void processBlock(Block firstBlock)
                {
                    if (firstBlock is Paragraph)
                    {
                        var para = (Paragraph) firstBlock;
                        IEnumerable<Run> runs = Enumerable.OfType<Run>(GetInlines(para.Inlines));
                        string t = string.Join("",
                                               Enumerable.ToArray(Enumerable.Select(Enumerable.ToArray(runs),
                                                                                    r => r.Text)));
                        Cell cell = _worksheet[row, column];
                        if (!string.IsNullOrEmpty(t))
                        {
                            double d;
                            if (double.TryParse(t, out d))
                                cell.Value = d;
                            else
                                cell.Value = t;
                        }
                        ImportFormatting(para);
                        if (para.Inlines.FirstInline != null)
                            ImportFormatting(para.Inlines.FirstInline);
                    }
                    else
                    {
                        throw new NotImplementedException();
                    }
                }

                private IEnumerable<Inline> GetInlines(InlineCollection inlines)
                {
                    foreach (Inline i in inlines)
                    {
                        yield return i;
                        if (i is Span)
                            foreach (Inline ii in GetInlines(((Span) i).Inlines))
                                yield return ii;
                    }
                }
            }

            #endregion

            //\\make private
        }

        #endregion
    }
}